package com.darkidiot.session.redis;

import com.darkidiot.session.conf.Configuration;
import com.darkidiot.session.exception.SessionException;
import com.darkidiot.session.serialize.JsonSerializer;
import com.darkidiot.session.serialize.KryoSerializer;
import com.darkidiot.session.serialize.Serializer;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;

import java.util.Collections;
import java.util.Map;

/**
 * session attribute持久化到redis
 * Copyright (c) for darkidiot
 * Date:2017/4/14
 * Author: <a href="darkidiot@icloud.com">darkidiot</a>
 * School: CUIT
 * Desc:
 */
@Slf4j
public class RedisSessionSource implements SessionSource {
    private volatile JedisPoolExecutor executor;
    private static Serializer serializer;
    private String sessionPrefix;
    private int dbIndex;

    public RedisSessionSource(Configuration configuration) {
        dbIndex = configuration.getSessionRedisDbIndex();
        switch (configuration.getSerializeType()) {
            case binary:
                serializer = new KryoSerializer();
                break;
            case json:
                serializer = new JsonSerializer();
                break;
        }
        sessionPrefix = configuration.getSessionRedisPrefix();
        JedisPoolConfig config = new JedisPoolConfig();
        config.setTestOnBorrow(configuration.getSessionRedisTestOnBorrow());
        config.setMaxIdle(configuration.getSessionRedisMaxIdle());
        config.setMaxTotal(configuration.getSessionRedisMaxTotal());
        executor = new JedisPoolExecutor(config, configuration.getSessionRedisCluster(), configuration);
    }

    @Override
    public Map<String, Object> findAttributeByMsid(String msid) {
        final String sessionId = getSessionId(msid);
        try {
            return executor.execute(new JedisCallback<Map<String, Object>>() {
                @Override
                public Map<String, Object> execute(Jedis jedis) {
                    String session = jedis.get(sessionId);
                    if (!Strings.isNullOrEmpty(session)) {
                        Map<String, Object> deserialize = serializer.deserialize(session);
                        log.debug("find session(key={}) in redis,value:{}", sessionId, deserialize);
                        return deserialize;
                    }
                    return Collections.emptyMap();
                }
            }, dbIndex);
        } catch (Exception e) {
            log.error("failed to find session(key={}) in redis,cause:{}", sessionId, Throwables.getStackTraceAsString(e));
            throw new SessionException("get session failed", e);
        }
    }

    @Override
    public boolean refreshExpireTime(String msid, final int maxInactiveInterval) {
        final String sessionId = getSessionId(msid);
        try {
            return executor.execute(new JedisCallback<Boolean>() {
                @Override
                public Boolean execute(Jedis jedis) {
                    Long ret = jedis.expire(sessionId, maxInactiveInterval);
                    log.debug("refresh expire time session(key={}) in redis,expire time:{}", sessionId, maxInactiveInterval);
                    return ret == 1;
                }
            }, dbIndex);
        } catch (Exception e) {
            log.error("failed to refresh expire time session(key={}) in redis,cause:{}", sessionId, Throwables.getStackTraceAsString(e));
        }
        return false;
    }

    @Override
    public boolean deletePhysically(String msid) {
        final String sessionId = getSessionId(msid);
        try {
            return executor.execute(new JedisCallback<Boolean>() {
                @Override
                public Boolean execute(Jedis jedis) {
                    jedis.del(sessionId);
                    log.debug("delete session(key={}) in redis", sessionId);
                    return true;
                }
            }, dbIndex);
        } catch (Exception e) {
            log.error("failed to delete session(key={}) in redis,cause:{}", sessionId, Throwables.getStackTraceAsString(e));
        }
        return false;
    }

    @Override
    public boolean save(String msid, final Map<String, Object> snapshot, final int maxInactiveInterval) {
        if (snapshot == null) {
            return false;
        }
        final String sessionId = getSessionId(msid);
        try {
            return executor.execute(new JedisCallback<Boolean>() {
                @Override
                public Boolean execute(Jedis jedis) {
                    if (snapshot.isEmpty()) {
                        jedis.del(sessionId);
                        log.debug("save session(key={}) in redis,value:{}", sessionId, snapshot);
                    } else {
                        String ret = jedis.setex(sessionId, maxInactiveInterval, serializer.serialize(snapshot));
                        log.debug("save session(key={}) in redis,value:{}", sessionId, snapshot);
                        return "OK".equals(ret);
                    }
                    return true;
                }
            }, dbIndex);
        } catch (Exception e) {
            log.error("failed to save session(key={}) in redis,cause:{}", sessionId, Throwables.getStackTraceAsString(e));
        }
        return false;
    }

    @Override
    public void destroy() {
        if (executor != null) {
            executor.destroy();
        }
    }


    private String getSessionId(String msid) {
        return sessionPrefix == null ? msid : sessionPrefix + ":" + msid;
    }
}
